
# Copyright 2009 Mithun Chaubey 
# 
# Licensed under the Apache License, Version 2.0 (the "License"); you 
# may not use this file except in compliance with the License. You may 
# obtain a copy of the License at 
# 
#     http://www.apache.org/licenses/LICENSE-2.0
#     
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing  permissions and
# limitations under the License.

''' Module with handler classes for timeline pages '''

from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

import lh_user
import timeline
import event

import os
from xml.dom import minidom
import urllib
import datetime
import re
from copy import copy

def simile_timeline_display_values(tl_rev):
    '''
    Calculate simile-timeline display values for timeline revision
    @param tl_rev: Revision for which to calculate display values
    @type tl_rev: timeline_revision
    '''

    # Set band intervals
    if(len(tl_rev.events()) < 2):
        # Defaults if there's 1 or 0 events
        largeband_interval = 'Timeline.DateTime.YEAR'
        smallband_interval = 'Timeline.DateTime.DECADE'
        largeband_interval_num = 10
        smallband_interval_num = 10
    else:
        # Calculate total range of time encompassed by timeline
        last_ev = tl_rev.last_event()
        first_ev = tl_rev.first_event()
        if last_ev.durationEvent:
            tl_range = last_ev.end - first_ev.start
        else:
            tl_range = last_ev.start - first_ev.start
            
        range_years = tl_range.days // 365

        # Set intervals based on timeline range, in years
        if(range_years < 1):
            largeband_interval = 'Timeline.DateTime.DAY'
            smallband_interval = 'Timeline.DateTime.MONTH'
            largeband_interval_num = 7
            smallband_interval_num = 12
        elif( 1 < range_years <= 10 ):
            largeband_interval = 'Timeline.DateTime.MONTH'
            smallband_interval = 'Timeline.DateTime.YEAR'
            largeband_interval_num = 12
            smallband_interval_num = range_years
        elif( 10 < range_years < 300):
            largeband_interval = 'Timeline.DateTime.YEAR'
            smallband_interval = 'Timeline.DateTime.DECADE'
            largeband_interval_num = 10
            smallband_interval_num = range_years / 10
        else:
            largeband_interval = 'Timeline.DateTime.DECADE'
            smallband_interval = 'Timeline.DateTime.CENTURY'
            largeband_interval_num = 10
            smallband_interval_num = range_years / 100

    # Set date to center on
    if(len(tl_rev.events()) == 0):
        start_date = None
    else:
        start_date = tl_rev.first_event().start

    values = {
        'largeband_interval': largeband_interval,
        'smallband_interval': smallband_interval,
        'largeband_interval_num': largeband_interval_num,
        'smallband_interval_num': smallband_interval_num,
        'start_date': start_date
        }

    return values

def date_str_from_datetime(xdate):
    '''
    Return date as formatted string for simile timeline
    @param xdate: Date to be formatted
    @type xdate: datetime.datetime
    @rtype: String
    '''
    year = xdate.year
    xdate = xdate.replace(year=1901)
    datestring = xdate.strftime('%b %d ') + str(year) + xdate.strftime(' %H:%M:%S GMT%z')

    return datestring

def datetime_from_date_str(string):
    '''
    Parses date string into datetime object 
    @param string: A formatted date string
    @type string: String
    @rtype: datetime.datetime
    '''
    # Empty
    if (string == ''):
        dt = None
    # Year only
    elif (re.match(r'^\d{1,4}$', string) != None):
        dt = datetime.datetime( int(re.match(r'^\d{1,4}$', string).group()), 01, 01 )
    # YYYY-MM-DD
    elif (re.match(r'^(\d{1,4})-(\d{1,2})-(\d{1,2})$', string) != None):
        match = re.match(r'^(\d{1,4})-(\d{1,2})-(\d{1,2})$', string)
        dt = datetime.datetime(int(match.group(1)), int(match.group(2)), int(match.group(3)))
    # YYYY-MM-DD HH:MM:SS
    elif (re.match(r'^(\d{1,4})-(\d{1,2})-(\d{1,2})\s\d{2}:\d{2}:\d{2}$', string) != None):
        match = re.match(r'^(\d{1,4})-(\d{1,2})-(\d{1,2})\s(\d{2}):(\d{2}):(\d{2})$', string)
        dt = datetime.datetime(int(match.group(1)), int(match.group(2)), int(match.group(3)),
                      int(match.group(4)), int(match.group(5)), int(match.group(6)))
    else:
        raise ValueError, 'Unable to convert string into datetime object: ' + string
        
    return dt

class simile_timelineXML(webapp.RequestHandler):
    '''Handler for generating XML for simile-timeline'''
    def get(self, key_str):
        '''
        Outputs XML for requested timeline revision for simile timeline
        @param key_str: Key string for timeline revision
        @type key_str: String
        '''
        current_user = lh_user.get_current_user(self)

        doc = minidom.Document()
        data_element = doc.createElementNS("data", "data")
        doc.appendChild(data_element)

        tl_rev = timeline.get_timeline_revision_by_key_str(key_str)
        if tl_rev.key() != tl_rev.entry.latest_revision().key():
            is_revision = True
        else:
            is_revision = False
        
        elements = []
        i = 0
        for ev_rev in tl_rev.revisions_linked_from(event.event_revision):
            elements.append(doc.createElementNS("data", "event"))

            elements[i].setAttributeNS("data", "start", date_str_from_datetime(ev_rev.start))
            if(ev_rev.end != None):
                elements[i].setAttributeNS("data", "end", date_str_from_datetime(ev_rev.end))

            if(ev_rev.durationEvent):
                elements[i].setAttributeNS("data", "isDuration", 'true')
            else:
                elements[i].setAttributeNS("data", "isDuration", 'false')

            elements[i].setAttributeNS("data", "title", ev_rev.title)

            template_values = {
                'timeline': tl_rev.entry,
                'timeline_revision': tl_rev,
                'event': ev_rev.entry,
                'event_revision': ev_rev,
                'is_revision': is_revision,
                'current_user': current_user
                }
            path = os.path.join(os.path.dirname(__file__), 'templates/eventPopup.html')
            text = template.render(path, template_values)
            elements[i].appendChild(doc.createTextNode(text))
            
            data_element.appendChild(elements[i])
            i = i + 1

        self.response.headers["Content-Type"] = "application/xml"
        self.response.out.write(doc.toprettyxml())

class showTimeline(webapp.RequestHandler):
    '''Handler for displaying timelines'''
#    def get(self, name, rev = None, rev_key = None):
    def get(self, name, rev_key = None):
        '''Display a timeline either through showTimeline.html or showTimelineRev.html'''

        # simile-timeline hack: It tries to write this file to your directory
        if(name == '__history__.html'):
            self.response.out.write('<html><body></body></html>')
        else:
            
            name = urllib.unquote(name)

            # Find timeline specified in URL
            tl = timeline.get_timeline_by_name(name)
            if not tl:
                self.response.out.write("Timeline not found: " + name)
            else:
                
                # If the name was typed in lowercase, for instance, redirect to the correct case as stored in datastore
                if(name != tl.name):
                    self.redirect('/timeline/' + tl.name)
                    
            
                # Check if revision is specified
                if rev_key:
                    tl_rev = timeline.get_timeline_revision_by_key_str(rev_key)
                    is_revision = True
                else:
                    tl_rev = tl.latest_revision()
                    is_revision = False
                    
                # Calculate simile timeline display variables
                display_vals = simile_timeline_display_values(tl_rev)
    
                # Output template                    
                if is_revision:
                    path = os.path.join(os.path.dirname(__file__), 'templates/showTimelineRevision.html')
                    activetab = "revisions"
                    alert = 'This is a revision: ' + date_str_from_datetime(tl_rev.date)
                else:
                    path = os.path.join(os.path.dirname(__file__), 'templates/showTimeline.html')
                    activetab = "timeline"
                    alert = None
    
                template_values = {
                    'timeline': tl,
                    'timeline_revision': tl_rev,
                    'largeband_interval': display_vals['largeband_interval'],
                    'smallband_interval': display_vals['smallband_interval'],
                    'largeband_interval_num': display_vals['largeband_interval_num'],
                    'smallband_interval_num': display_vals['smallband_interval_num'],
                    'start_date': display_vals['start_date'],
                    'current_user': lh_user.get_current_user(self),
                    'activetab': activetab
                    }
                
                if alert:
                    template_values['alert'] = alert
                
                self.response.out.write(template.render(path, template_values))

class createTimeline(webapp.RequestHandler):
    '''Handler for timeline creation form'''
    def post(self):
        '''Create new timeline from form data'''

        # Check permissions
        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_edit'):
            self.response.out.write("Not authorized")
        else:
            if timeline.timeline_exists_by_name(self.request.get('name')):
                self.response.out.write("Error: timeline by that name exists")
            else:
                new_tl = timeline.create_new_timeline(self.request.get('name'),
                                                      lh_user.get_current_user(self),
                                                      self.request.get('description'))
                
                if new_tl:
                    if self.request.get('url') != '':
                        self.redirect(self.request.get('url'))
                    else:
                        self.redirect('/timeline/' + new_tl.name)
                else:
                    self.response.out.write("Error creating timeline")
                    
    def get(self):
        ''' Show form for creating new timeline using createTimeline.html'''
        
        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_create'):
            alert = "Permission denied"
        else:
            alert = None
        
        template_values = { 'current_user': current_user,
                            'alert': alert
                            }

        path = os.path.join(os.path.dirname(__file__), 'templates/createTimeline.html')
        self.response.out.write(template.render(path, template_values))


class editTimeline(webapp.RequestHandler):
    '''Handler for editing a timeline'''
    def get(self, name):
        '''
        Display form for editing timeline using editTimeline.html
        @param name: The name of the timeline being edited
        @type name: String
        '''

        # Check permissions
        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_edit'):
            self.response.out.write('Not authorized')
        else:
            
            name = urllib.unquote(name)

            # Get timeline
            tl = timeline.get_timeline_by_name(name)
    
            # If the timeline doesn't exist, send them to the showTimeline handler so they get an error message
            if not tl:
                self.redirect('/timeline/'+name)

            # Simply output the template with the edit timeline form
            template_values = { 'timeline' : tl,
                                'timeline_revision': tl.latest_revision(),
                                'current_user': current_user,
                                'activetab': "edit"
                                }

            path = os.path.join(os.path.dirname(__file__), 'templates/editTimeline.html')
            self.response.out.write(template.render(path, template_values))

    def post(self, name):
        '''
        Create new timeline revision based on editTimelineForm data
        @param name: The name of the timeline being edited
        @type name: String
        '''

        # Check permissions
        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_edit'):
            self.response.out.write("Not authorized")
        else:
            
            name = urllib.unquote(name)

            # Get timeline
            tl = timeline.get_timeline_by_name(name)
            
            # If timeline doesn't exist, redirect to showTimeline handler for error message
            if not tl:
                self.redirect('/timeline/'+name)
            else:
                
                tl_rev_new = timeline.timeline_revision()
                tl_rev_new.description = self.request.get('description')

                # Store new revision
                tl.new_revision(tl_rev_new,
                                self.request.get('comment'),
                                current_user)
    
                self.redirect('/timeline/' + self.request.get('name'))
            
class showTimelineRevisions(webapp.RequestHandler):
    '''Handler for viewing list of timeline revisions'''
    def get(self, name):
        '''
        Displays list of timeline revisions using showTimelineRevisions.html
        @param name: Name of the timeline
        @type name: String
        '''
        
        name = urllib.unquote(name)

        # Get timeline
        tl = timeline.get_timeline_by_name(name)

        if tl == None:
            self.redirect('/timeline/' + name)
        else:
            # Get all revisions
            revs = tl.all_revisions()
    
            # Send to template
            template_values = {
                            'timeline': tl,
                            'revisions': revs,
                            'current_user': lh_user.get_current_user(self),
                            'activetab': "revisions"
                            }
            
            path = os.path.join(os.path.dirname(__file__), 'templates/showTimelineRevisions.html')
            self.response.out.write(template.render(path, template_values))
            
class compareTimelineRevs(webapp.RequestHandler):
    ''' Hander for timeline revision comparison '''
    def get(self, name, args):
        self.response.out.write('Not implemented yet')
#        '''
#        Displays a comparison of two revisions of a timeline using compareTimelineRevs.html
#        @param name: The name of the timeline
#        @type name: String
#        @param args: URL Get parameters
#        '''
#
#        tl = get_timeline_by_name(name)
#
#        oldrev_key_str = self.request.get('oldrev')
#        newrev_key_str = self.request.get('newrev')
#
#        oldrev = get_tl_revision_by_key_str(oldrev_key_str)
#        newrev = get_tl_revision_by_key_str(newrev_key_str)
#
#        # Check that both revisions belong to named timeline
#        if(oldrev.rev_item.key() != tl.key() or newrev.rev_item.key() != tl.key()):
#            raise ValueError
#            sys.exit()
#        
#        # Check for changed timeline description
#        if(oldrev.description == newrev.description):
#            changedDescription = False
#        else:
#            changedDescription = True
#
#        changed_evs = []
#        deleted_evs = []
#        new_evs = []
#
#        # Check for changed and deleted events (exists in oldrev but not newrev)
#        for ev in oldrev.event_set:
#            for ev_new in newrev.event_set:
#                if (ev.orig.key() == ev_new.orig.key()):
#                    if event_diff(ev, ev_new):
#                        changed_evs.append( (ev, ev_new) )
#                        break
#            else:
#                deleted_evs.append(ev)
#
#        # Check for new events (exists in newrev but not oldrev -- changed events already taken care of)
#        for ev in newrev.event_set:
#            for ev_old in oldrev.event_set:
#                if(ev.orig.key() == ev_old.orig.key()):
#                    break
#            else:
#                new_evs.append(ev)
#
#        # Output
#        template_values = {
#                        'timeline': tl,
#                        'oldrev': oldrev,
#                        'newrev': newrev,
#                        'changedDescription': changedDescription,
#                        'new_evs': new_evs,
#                        'changed_evs': changed_evs,
#                        'deleted_evs': deleted_evs,
#                        'current_user': lh_user.get_current_user(self),
#                        'activetab': "revisions"
#                        }
#
#        path = os.path.join(os.path.dirname(__file__), 'templates/compareTimelineRevs.html')
#        self.response.out.write(template.render(path, template_values))

class deleteTimeline(webapp.RequestHandler):
    '''Handler for deleting a timeline'''
    def get(self, name):
        '''
        Display page to confirm deleting a timeline using confirmDeleteTimeline.html
        @param name: The name of the timeline
        @type name: String
        '''

        # Check permissions
        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_delete'):
            self.response.out.write("Not authorized")
        else:
            
            name = urllib.unquote(name)

            # Get timeline
            tl = timeline.get_timeline_by_name(name)

            # If timeline doesn't exist, redirect to showTimeline handler for error message
            if not tl:
                self.redirect('/timeline/'+name)

            # Output template
            template_values = {
                'timeline' : tl,
                #'timeline_revision' : tl.latest_revision(),
                'current_user': current_user,
                'activetab' : "delete"
                }

            path = os.path.join(os.path.dirname(__file__), 'templates/confirmDeleteTimeline.html')
            self.response.out.write(template.render(path, template_values))

    def post(self, name):
        '''
        Delete a timeline from the datastore
        @param name: Name of the timeline to delete
        @type name: String
        '''

        # Check permissions
        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_delete'):
            self.response.out.write("Not authorized")
        else:
            
            name = urllib.unquote(name)
            tl = timeline.get_timeline_by_name(name)
            
            if not tl:
                self.redirect('/timeline/'+name)
            else:
                tl.delete()
                self.redirect('/')
                
class createTimelineEvent(webapp.RequestHandler):
    ''' Handler for adding a new event to a timeline '''
    def post(self, tl_name):
        '''Create new timeline event from form data'''

        # Check user role
        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_edit'):
            self.response.out.write("Not authorized")
        else:
            
            tl_name = urllib.unquote(tl_name)
            tl = timeline.get_timeline_by_name(tl_name)
            if not tl:
                self.redirect('/timeline/'+name)
            else:
                
                # Build initial event revision from form data
                ev_rev = event.event_revision()
                ev_rev.start = datetime_from_date_str(self.request.get('start'))
                ev_rev.latestStart = datetime_from_date_str(self.request.get('latestStart'))
                ev_rev.end = datetime_from_date_str(self.request.get('end'))
                ev_rev.earliestEnd = datetime_from_date_str(self.request.get('earliestEnd'))
                ev_rev.title = self.request.get('title')
                ev_rev.description = self.request.get('description')
                if self.request.get('end') != '':
                    ev_rev.durationEvent = True
                else:
                    ev_rev.durationEvent = False
                
                # Create new event from revision
                ev = event.create_new_event(ev_rev, current_user)
                
                # Create new timeline revision
                tl_rev_new = tl.latest_revision().copy()
                tl.new_revision(tl_rev_new, 'New event: '+ev_rev.title, current_user)
                
                # Link first event revision to new timeline revision                
                ev_rev.link_to(tl_rev_new)
                
                
                self.redirect('/timeline/' + tl_name)
                
class editTimelineEvent(webapp.RequestHandler):
    '''Handler for editing an event'''
    def get(self, tl_name, key_str):
        '''
        Displays form for editing an event using editEvent.html
        @param tl_name: The name of the timeline from which the event will be edited
        @param key_str: Key string for event object
        @type tl_name: String
        @type key_str: String
        '''

        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_edit'):
            self.response.out.write("Not authorized")
        else:
            tl_name = urllib.unquote(tl_name)
            tl = timeline.get_timeline_by_name(tl_name)
            if not tl:
                self.redirect('/timeline/' + tl_name)
            else:
                
                ev = event.get_event_by_key_str(key_str)
    
                template_values = { 'event' : ev,
                                    'event_revision' : ev.latest_revision(),
                                    'timeline': tl,
                                    'timeline_revision': tl.latest_revision(),
                                    'current_user': current_user,
                                    'activetab': "edit"
                                    }
                path = os.path.join(os.path.dirname(__file__), 'templates/editTimelineEvent.html')
                self.response.out.write(template.render(path, template_values))
    
    def post(self, tl_name, key_str):
        '''
        Edits an event using timeline form values
        @param name: The name of the timeline from which the edit form is submitted
        @param key_str: Key value for the event being modified
        @type name: String
        @type key_str: String
        '''

        # Check user
        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_edit'):
            self.response.out.write("Not authorized")
        else:
            
            tl_name = urllib.unquote(tl_name)
            tl = timeline.get_timeline_by_name(tl_name)
            if not tl:
                self.redirect('/timeline' + tl_name)
            else:

                # Get old event revision
                ev = event.get_event_by_key_str(key_str)
                ev_rev_old = ev.latest_revision()
                
                if not ev_rev_old.is_linked_to(tl.latest_revision()):
                    raise ValueError, "Event does not belong to timeline: " + tl_name
                
                # Build event revision from form data
                ev_rev = event.event_revision()
                ev_rev.start = datetime_from_date_str(self.request.get('start'))
                ev_rev.latestStart = datetime_from_date_str(self.request.get('latestStart'))
                ev_rev.end = datetime_from_date_str(self.request.get('end'))
                ev_rev.earliestEnd = datetime_from_date_str(self.request.get('earliestEnd'))
                ev_rev.title = self.request.get('title')
                ev_rev.description = self.request.get('description')
                if self.request.get('end') != '':
                    ev_rev.durationEvent = True
                else:
                    ev_rev.durationEvent = False
            
                ev.new_revision(ev_rev, self.request.get('comment'), current_user)
            
                if(self.request.get('url') != ''):
                    self.redirect(self.request.get('url'))
                else:
                    self.redirect('/timeline/' + tl_name)

class deleteTimelineEvent(webapp.RequestHandler):
    '''Handler for deleting an event'''
    def get(self, tl_name, key_str):
        '''
        Displays page to confirm deleting an event, using confirmTimelineDeleteEvent.html
        @param tl_name: Name of the timeline
        @param key_str: Key string of the event
        @type tl_name: String
        @type key_str: String
        '''

        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_delete'):
            self.response.out.write("Not authorized")
        else:
            tl_name = urllib.unquote(tl_name)
            tl = timeline.get_timeline_by_name(tl_name)
            if not tl:
                raise ValueError, "Timeline not found: " + tl_name

            ev = event.get_event_by_key_str(key_str)
            template_values = {
                'event': ev,
                'event_revision': ev.latest_revision(),
                'timeline': tl,
                'current_user': current_user,
                'activetab': "edit"
                }
            path = os.path.join(os.path.dirname(__file__), 'templates/confirmDeleteTimelineEvent.html')
            self.response.out.write(template.render(path, template_values))

    def post(self, tl_name, key_str):
        '''
        Creates a new timeline revision without the deleted event
        @param tl_name: Name of the timeline
        @param key_str: Key string of the event
        @type tl_name: String
        @type key_str: String
        '''

        current_user = lh_user.get_current_user(self)
        if not current_user.has_perm('timeline_edit'):
            self.response.out.write("Not authorized")
        else:
            
            tl_name = urllib.unquote(tl_name)
            tl = timeline.get_timeline_by_name(tl_name)
            if not tl:
                raise ValueError, "Timeline not found: " + tl_name
            
            tl_rev_new = tl.latest_revision().copy()
            tl.new_revision(tl_rev_new, self.request.get('comment'), current_user)
            
            ev = event.get_event_by_key_str(key_str)
            tl_rev_new.unlink_from(ev.latest_revision())

            self.redirect('/timeline/' + tl_name)